// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

`include "prim_assert.sv"

module rom_ctrl
  import rom_ctrl_reg_pkg::NumAlerts;
  import prim_rom_pkg::rom_cfg_t;
#(
  parameter                       BootRomInitFile = "",
  parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}},
  parameter bit [63:0]            RndCnstScrNonce = '0,
  parameter bit [127:0]           RndCnstScrKey = '0,

  // Disable all (de)scrambling operation. This disables both the scrambling block and the boot-time
  // checker. Don't use this in a real chip, but it's handy for small FPGA targets where we don't
  // want to spend area on unused scrambling.
  parameter bit                   SecDisableScrambling = 1'b0
) (
  input  clk_i,
  input  rst_ni,

  // ROM configuration parameters
  input  rom_cfg_t rom_cfg_i,

  input  tlul_pkg::tl_h2d_t rom_tl_i,
  output tlul_pkg::tl_d2h_t rom_tl_o,

  input  tlul_pkg::tl_h2d_t regs_tl_i,
  output tlul_pkg::tl_d2h_t regs_tl_o,

  // Alerts
  input  prim_alert_pkg::alert_rx_t [NumAlerts-1:0] alert_rx_i,
  output prim_alert_pkg::alert_tx_t [NumAlerts-1:0] alert_tx_o,

  // Connections to other blocks
  output rom_ctrl_pkg::pwrmgr_data_t pwrmgr_data_o,
  output rom_ctrl_pkg::keymgr_data_t keymgr_data_o,
  input  kmac_pkg::app_rsp_t         kmac_data_i,
  output kmac_pkg::app_req_t         kmac_data_o
);

  import rom_ctrl_pkg::*;
  import rom_ctrl_reg_pkg::*;
  import prim_mubi_pkg::mubi4_t, prim_mubi_pkg::MuBi4True;
  import prim_util_pkg::vbits;

  `define CLK_WAIT_BOUNDS ##[MIN_CLK_WAIT_CYCLES:MAX_CLK_WAIT_CYCLES]
  // ROM_CTRL_ROM_SIZE is auto-generated by regtool and comes from the bus window size, measured in
  // bytes of content (i.e. 4 times the number of 32 bit words).
  localparam int unsigned RomSizeByte = ROM_CTRL_ROM_SIZE;
  localparam int unsigned RomSizeWords = RomSizeByte >> 2;
  localparam int unsigned RomIndexWidth = vbits(RomSizeWords);

  // DataWidth is normally 39, representing 32 bits of actual data plus 7 ECC check bits. If
  // scrambling is disabled ("insecure mode"), we store a raw 32-bit image and generate ECC check
  // bits on the fly.
  localparam int unsigned DataWidth = SecDisableScrambling ? 32 : 39;

  mubi4_t                   rom_select_bus;

  logic [RomIndexWidth-1:0] rom_rom_index, rom_prince_index;
  logic                     rom_req;
  logic [DataWidth-1:0]     rom_scr_rdata;
  logic [DataWidth-1:0]     rom_clr_rdata;
  logic                     rom_rvalid;

  logic [RomIndexWidth-1:0] bus_rom_rom_index, bus_rom_prince_index;
  logic                     bus_rom_req;
  logic                     bus_rom_gnt;
  logic [DataWidth-1:0]     bus_rom_rdata;
  logic                     bus_rom_rvalid, bus_rom_rvalid_raw;

  logic [RomIndexWidth-1:0] checker_rom_index;
  logic                     checker_rom_req;
  logic [DataWidth-1:0]     checker_rom_rdata;

  logic                     internal_alert;

  // Pack / unpack kmac connection data ========================================

  logic [63:0]              kmac_rom_data;
  logic                     kmac_rom_rdy;
  logic                     kmac_rom_vld;
  logic                     kmac_rom_last;
  logic                     kmac_done;
  logic [255:0]             kmac_digest;
  logic                     kmac_err;

  if (!SecDisableScrambling) begin : gen_kmac_scramble_enabled
    // The usual situation, with scrambling enabled. Collect up output signals for kmac and split up
    // the input struct into separate signals.

    // Neglecting any first / last block effects, and assuming that ROM_CTRL can always fill the
    // KMAC message FIFO while a KMAC round is running, the total processing time for a 32kB ROM is
    // calculated as follows:
    //
    // (Padding Overhead) x (ROM Size) / (Block Size) x (Block Processing Time + KMAC Absorb Time)
    //
    // ROM_CTRL can only read out one 32 or 39 bit (with ECC) word per cycle, so if we were to zero
    // pad this to align with the 64bit KMAC interface, the padding overhead would amount to 2x
    // in this equation:
    //
    // 2 x 32 kByte / (1600 bit - 2x 256bit) x (96 cycles + (1600 bit - 2x 256bit) / 64bit)) =
    // 2 x 32 x 1024 x 8bit / 1088bit x (96 cycles + 17 cycles) =
    // 2 x 262144 bit / 1088 bit x 113 cycles =
    // 2 x 27226.35 cycles
    //
    // Luckily, the KMAC interface allows to transmit data with a byte enable mask, and only the
    // enabled bytes will be packed into the message FIFO. Assuming that the processing is the
    // bottleneck, we can thus reduce the overhead of 2x in that equation to 1x or 5/8x if we only
    // set 4 or 5 byte enables (4 for 32bit, 5 for 39bit)!
    localparam int NumBytes = (DataWidth + 7) / 8;

    // SEC_CM: MEM.DIGEST
    assign kmac_data_o = '{valid: kmac_rom_vld,
                           data: kmac_rom_data,
                           strb: kmac_pkg::MsgStrbW'({NumBytes{1'b1}}),
                           last: kmac_rom_last};

    assign kmac_rom_rdy = kmac_data_i.ready;
    assign kmac_done = kmac_data_i.done;
    assign kmac_digest = kmac_data_i.digest_share0[255:0] ^ kmac_data_i.digest_share1[255:0];
    assign kmac_err = kmac_data_i.error;

    logic unused_kmac_digest;
    assign unused_kmac_digest = ^{
      kmac_data_i.digest_share0[kmac_pkg::AppDigestW-1:256],
      kmac_data_i.digest_share1[kmac_pkg::AppDigestW-1:256]
    };

  end : gen_kmac_scramble_enabled
  else begin : gen_kmac_scramble_disabled
    // Scrambling is disabled. Stub out all KMAC connections and waive the ignored signals.

    assign kmac_data_o = '0;
    assign kmac_rom_rdy = 1'b0;
    assign kmac_done = 1'b0;
    assign kmac_digest = '0;
    assign kmac_err = 1'b0;

    logic unused_kmac_inputs;
    assign unused_kmac_inputs = ^{kmac_data_i};

    logic unused_kmac_outputs;
    assign unused_kmac_outputs = ^{kmac_rom_vld, kmac_rom_data, kmac_rom_last};

  end : gen_kmac_scramble_disabled

  // TL interface ==============================================================

  tlul_pkg::tl_h2d_t tl_rom_h2d_upstream, tl_rom_h2d_downstream;
  tlul_pkg::tl_d2h_t tl_rom_d2h;

  logic  rom_reg_integrity_error;

  rom_ctrl_rom_reg_top u_rom_top (
    .clk_i,
    .rst_ni,
    .tl_i       (rom_tl_i),
    .tl_o       (rom_tl_o),
    .tl_win_o   (tl_rom_h2d_upstream),
    .tl_win_i   (tl_rom_d2h),

    .intg_err_o (rom_reg_integrity_error),    // SEC_CM: BUS.INTEGRITY

    .devmode_i  (1'b1)
  );

  // This buffer ensures that when we calculate bus_rom_prince_index by snooping on
  // tl_rom_h2d_upstream, we get a value that's buffered from the thing that goes into both the ECC
  // check and the addr_o output of u_tl_adapter_rom. That way, an injected 1- or 2-bit fault that
  // affects bus_rom_prince_index must either affect the ECC check (causing it to fail) OR it cannot
  // affect bus_rom_rom_index (so the address-tweakable scrambling will mean the read probably gets
  // garbage).
  //
  // SEC_CM: CTRL.REDUN
  prim_buf #(
    .Width($bits(tlul_pkg::tl_h2d_t))
  ) u_tl_rom_h2d_buf (
    .in_i (tl_rom_h2d_upstream),
    .out_o (tl_rom_h2d_downstream)
  );

  // Bus -> ROM adapter ========================================================

  logic rom_integrity_error;

  tlul_adapter_sram #(
    .SramAw(RomIndexWidth),
    .SramDw(32),
    .Outstanding(2),
    .ByteAccess(0),
    .ErrOnWrite(1),
    .CmdIntgCheck(1),
    .EnableRspIntgGen(1),
    .EnableDataIntgGen(SecDisableScrambling),
    .EnableDataIntgPt(!SecDisableScrambling), // SEC_CM: BUS.INTEGRITY
    .SecFifoPtr      (1)                      // SEC_CM: TLUL_FIFO.CTR.REDUN
  ) u_tl_adapter_rom (
    .clk_i,
    .rst_ni,

    .tl_i         (tl_rom_h2d_downstream),
    .tl_o         (tl_rom_d2h),
    .en_ifetch_i  (prim_mubi_pkg::MuBi4True),
    .req_o        (bus_rom_req),
    .req_type_o   (),
    .gnt_i        (bus_rom_gnt),
    .we_o         (),
    .addr_o       (bus_rom_rom_index),
    .wdata_o      (),
    .wmask_o      (),
    .intg_error_o (rom_integrity_error),
    .rdata_i      (bus_rom_rdata),
    .rvalid_i     (bus_rom_rvalid),
    .rerror_i     (2'b00)
  );

  // Snoop on the "upstream" TL transaction to infer the address to pass to the PRINCE cipher.
  assign bus_rom_prince_index = (tl_rom_h2d_upstream.a_valid ?
                                 tl_rom_h2d_upstream.a_address[2 +: RomIndexWidth] :
                                 '0);

  // Unless there has been an injected fault, bus_rom_prince_index and bus_rom_rom_index should have
  // the same value.
  `ASSERT(BusRomIndicesMatch_A, bus_rom_prince_index == bus_rom_rom_index)

  // The mux ===================================================================

  logic mux_alert;

  rom_ctrl_mux #(
    .AW (RomIndexWidth),
    .DW (DataWidth)
  ) u_mux (
    .clk_i,
    .rst_ni,
    .sel_bus_i         (rom_select_bus),
    .bus_rom_addr_i    (bus_rom_rom_index),
    .bus_prince_addr_i (bus_rom_prince_index),
    .bus_req_i         (bus_rom_req),
    .bus_gnt_o         (bus_rom_gnt),
    .bus_rdata_o       (bus_rom_rdata),
    .bus_rvalid_o      (bus_rom_rvalid_raw),
    .chk_addr_i        (checker_rom_index),
    .chk_req_i         (checker_rom_req),
    .chk_rdata_o       (checker_rom_rdata),
    .rom_rom_addr_o    (rom_rom_index),
    .rom_prince_addr_o (rom_prince_index),
    .rom_req_o         (rom_req),
    .rom_scr_rdata_i   (rom_scr_rdata),
    .rom_clr_rdata_i   (rom_clr_rdata),
    .rom_rvalid_i      (rom_rvalid),
    .alert_o           (mux_alert)
  );

  // Squash all responses from the ROM to the bus if there's an internal integrity error from the
  // checker FSM or the mux. This avoids having to handle awkward corner cases in the mux: if
  // something looks bad, we'll complain and hang the bus transaction.
  //
  // Note that the two signals that go into internal_alert are both sticky. The mux explicitly
  // latches its alert_o output and the checker FSM jumps to an invalid scrap state when it sees an
  // error which, in turn, sets checker_alert.
  //
  // SEC_CM: BUS.LOCAL_ESC
  assign bus_rom_rvalid = bus_rom_rvalid_raw & !internal_alert;

  // The ROM itself ============================================================

  if (!SecDisableScrambling) begin : gen_rom_scramble_enabled

    // SEC_CM: MEM.SCRAMBLE
    rom_ctrl_scrambled_rom #(
      .MemInitFile (BootRomInitFile),
      .Width       (DataWidth),
      .Depth       (RomSizeWords),
      .ScrNonce    (RndCnstScrNonce),
      .ScrKey      (RndCnstScrKey)
    ) u_rom (
      .clk_i,
      .rst_ni,
      .req_i         (rom_req),
      .rom_addr_i    (rom_rom_index),
      .prince_addr_i (rom_prince_index),
      .rvalid_o      (rom_rvalid),
      .scr_rdata_o   (rom_scr_rdata),
      .clr_rdata_o   (rom_clr_rdata),
      .cfg_i         (rom_cfg_i)
    );

  end : gen_rom_scramble_enabled
  else begin : gen_rom_scramble_disabled

    // If scrambling is disabled then instantiate a normal ROM primitive (no PRINCE cipher etc.).
    // Note that this "raw memory" doesn't have ECC bits either.

    prim_rom_adv #(
      .Width       (DataWidth),
      .Depth       (RomSizeWords),
      .MemInitFile (BootRomInitFile)
    ) u_rom (
      .clk_i,
      .rst_ni,
      .req_i    (rom_req),
      .addr_i   (rom_rom_index),
      .rvalid_o (rom_rvalid),
      .rdata_o  (rom_scr_rdata),
      .cfg_i    (rom_cfg_i)
    );

    // There's no scrambling, so "scrambled" and "clear" rdata are equal.
    assign rom_clr_rdata = rom_scr_rdata;

    // Since we're not generating a keystream, we don't use the rom_prince_index at all
    logic unused_prince_index;
    assign unused_prince_index = ^rom_prince_index;

  end : gen_rom_scramble_disabled

  // Zero expand checker rdata to pass to KMAC
  assign kmac_rom_data = {{64-DataWidth{1'b0}}, checker_rom_rdata};

  // Register block ============================================================

  rom_ctrl_regs_reg2hw_t reg2hw;
  rom_ctrl_regs_hw2reg_t hw2reg;
  logic                  reg_integrity_error;

  rom_ctrl_regs_reg_top u_reg_regs (
    .clk_i,
    .rst_ni,
    .tl_i       (regs_tl_i),
    .tl_o       (regs_tl_o),
    .reg2hw     (reg2hw),
    .hw2reg     (hw2reg),
    .intg_err_o (reg_integrity_error),    // SEC_CM: BUS.INTEGRITY
    .devmode_i  (1'b1)
   );

  // The checker FSM ===========================================================

  logic [255:0] digest_q, exp_digest_q;
  logic [255:0] digest_d;
  logic         digest_de;
  logic [31:0]  exp_digest_word_d;
  logic         exp_digest_de;
  logic [2:0]   exp_digest_idx;

  logic         checker_alert;

  if (!SecDisableScrambling) begin : gen_fsm_scramble_enabled

    rom_ctrl_fsm #(
      .RomDepth (RomSizeWords),
      .TopCount (8)
    ) u_checker_fsm (
      .clk_i,
      .rst_ni,
      .digest_i             (digest_q),
      .exp_digest_i         (exp_digest_q),
      .digest_o             (digest_d),
      .digest_vld_o         (digest_de),
      .exp_digest_o         (exp_digest_word_d),
      .exp_digest_vld_o     (exp_digest_de),
      .exp_digest_idx_o     (exp_digest_idx),
      .pwrmgr_data_o        (pwrmgr_data_o),
      .keymgr_data_o        (keymgr_data_o),
      .kmac_rom_rdy_i       (kmac_rom_rdy),
      .kmac_rom_vld_o       (kmac_rom_vld),
      .kmac_rom_last_o      (kmac_rom_last),
      .kmac_done_i          (kmac_done),
      .kmac_digest_i        (kmac_digest),
      .kmac_err_i           (kmac_err),
      .rom_select_bus_o     (rom_select_bus),
      .rom_addr_o           (checker_rom_index),
      .rom_req_o            (checker_rom_req),
      .rom_data_i           (checker_rom_rdata[31:0]),
      .alert_o              (checker_alert)
    );

  end : gen_fsm_scramble_enabled
  else begin : gen_fsm_scramble_disabled

    // If scrambling is disabled, there's no checker FSM.

    assign digest_d = '0;
    assign digest_de = 1'b0;
    assign exp_digest_word_d = '0;
    assign exp_digest_de = 1'b0;
    assign exp_digest_idx = '0;

    assign pwrmgr_data_o = PWRMGR_DATA_DEFAULT;
    // Send something other than '1 or '0 because the key manager has an "all ones" and an "all
    // zeros" check.
    assign keymgr_data_o = '{data: {128{2'b10}}, valid: 1'b1};

    assign kmac_rom_vld = 1'b0;
    assign kmac_rom_last = 1'b0;

    // Always grant access to the bus. Setting this to a constant should mean the mux gets
    // synthesized away completely.
    assign rom_select_bus = MuBi4True;

    assign checker_rom_index = '0;
    assign checker_rom_req = 1'b0;
    assign checker_alert = 1'b0;

    logic unused_fsm_inputs;
    assign unused_fsm_inputs = ^{kmac_rom_rdy, kmac_done, kmac_digest, digest_q, exp_digest_q};

  end : gen_fsm_scramble_disabled

  // Register data =============================================================

  // DIGEST and EXP_DIGEST registers

  // Repack signals to convert between the view expected by rom_ctrl_reg_pkg for CSRs and the view
  // expected by rom_ctrl_fsm. Register 0 of a multi-reg appears as the low bits of the packed data.
  for (genvar i = 0; i < 8; i++) begin: gen_csr_digest
    localparam int unsigned TopBitInt = 32 * i + 31;
    localparam bit [7:0] TopBit = TopBitInt[7:0];

    assign hw2reg.digest[i].d = digest_d[TopBit -: 32];
    assign hw2reg.digest[i].de = digest_de;

    assign hw2reg.exp_digest[i].d = exp_digest_word_d;
    assign hw2reg.exp_digest[i].de = exp_digest_de && (i[2:0] == exp_digest_idx);

    assign digest_q[TopBit -: 32] = reg2hw.digest[i].q;
    assign exp_digest_q[TopBit -: 32] = reg2hw.exp_digest[i].q;
  end

  logic bus_integrity_error;
  assign bus_integrity_error = rom_reg_integrity_error | rom_integrity_error | reg_integrity_error;

  assign internal_alert = checker_alert | mux_alert;

  // FATAL_ALERT_CAUSE register
  assign hw2reg.fatal_alert_cause.checker_error.d  = internal_alert;
  assign hw2reg.fatal_alert_cause.checker_error.de = internal_alert;
  assign hw2reg.fatal_alert_cause.integrity_error.d  = bus_integrity_error;
  assign hw2reg.fatal_alert_cause.integrity_error.de = bus_integrity_error;

  // Alert generation ==========================================================

  logic [NumAlerts-1:0] alert_test;
  assign alert_test[AlertFatal] = reg2hw.alert_test.q &
                                  reg2hw.alert_test.qe;

  logic [NumAlerts-1:0] alerts;
  assign alerts[AlertFatal] = bus_integrity_error | checker_alert | mux_alert;

  for (genvar i = 0; i < NumAlerts; i++) begin: gen_alert_tx
    prim_alert_sender #(
      .AsyncOn(AlertAsyncOn[i]),
      .IsFatal(i == AlertFatal)
    ) u_alert_sender (
      .clk_i,
      .rst_ni,
      .alert_test_i  ( alert_test[i] ),
      .alert_req_i   ( alerts[i]     ),
      .alert_ack_o   (               ),
      .alert_state_o (               ),
      .alert_rx_i    ( alert_rx_i[i] ),
      .alert_tx_o    ( alert_tx_o[i] )
    );
  end

  // Asserts ===================================================================
  //
  // "ROM" TL interface: The d_valid and a_ready signals should be unconditionally defined. The
  // other signals in rom_tl_o (which are the other D channel signals) should be defined if d_valid.
  `ASSERT_KNOWN(RomTlODValidKnown_A, rom_tl_o.d_valid)
  `ASSERT_KNOWN(RomTlOAReadyKnown_A, rom_tl_o.a_ready)
  `ASSERT_KNOWN_IF(RomTlODDataKnown_A, rom_tl_o, rom_tl_o.d_valid)

  // "regs" TL interface: The d_valid and a_ready signals should be unconditionally defined. The
  // other signals in rom_tl_o (which are the other D channel signals) should be defined if d_valid.
  `ASSERT_KNOWN(RegsTlODValidKnown_A, regs_tl_o.d_valid)
  `ASSERT_KNOWN(RegsTlOAReadyKnown_A, regs_tl_o.a_ready)
  `ASSERT_KNOWN_IF(RegsTlODDataKnown_A, regs_tl_o, regs_tl_o.d_valid)

  // The assert_tx_o output should have a known value when out of reset
  `ASSERT_KNOWN(AlertTxOKnown_A, alert_tx_o)

  // Assertions to check that we've wired up our alert bits correctly
  if (!SecDisableScrambling) begin : gen_asserts_with_scrambling
    `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CompareFsmAlert_A,
                                         gen_fsm_scramble_enabled.
                                         u_checker_fsm.u_compare.u_state_regs,
                                         alert_tx_o[AlertFatal])
    `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(CheckerFsmAlert_A,
                                         gen_fsm_scramble_enabled.
                                         u_checker_fsm.u_state_regs,
                                         alert_tx_o[AlertFatal])
    `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(CompareAddrCtrCheck_A,
                                           gen_fsm_scramble_enabled.
                                           u_checker_fsm.u_compare.u_prim_count_addr,
                                           alert_tx_o[AlertFatal])
  end

  // The pwrmgr_data_o output (the "done" and "good" signals) should have a known value when out of
  // reset. (In theory, the "good" signal could be unknown when !done, but the stronger and simpler
  // assertion is also true, so we use that)
  `ASSERT_KNOWN(PwrmgrDataOKnown_A, pwrmgr_data_o)

  // The valid signal for keymgr_data_o should always be known when out of reset. The rest of the
  // struct (a data signal) should be known whenever the valid signal is true.
  `ASSERT_KNOWN(KeymgrDataOValidKnown_A, keymgr_data_o.valid)
  `ASSERT_KNOWN_IF(KeymgrDataODataKnown_A, keymgr_data_o, keymgr_data_o.valid)

  // The valid signal for kmac_data_o should always be known when out of reset. The rest of the
  // struct (data, strb and last) should be known whenever the valid signal is true.
  `ASSERT_KNOWN(KmacDataOValidKnown_A, kmac_data_o.valid)
  `ASSERT_KNOWN_IF(KmacDataODataKnown_A, kmac_data_o, kmac_data_o.valid)

  // Check that pwrmgr_data_o.good is stable when kmac_data_o.valid is asserted
  `ASSERT(StabilityChkKmac_A, kmac_data_o.valid && $past(kmac_data_o.valid)
          |-> $stable(pwrmgr_data_o.good))

  // Check that pwrmgr_data_o.good is stable when keymgr_data_o.valid is asserted
  `ASSERT(StabilityChkkeymgr_A, keymgr_data_o.valid && $past(keymgr_data_o.valid)
          |-> $stable(pwrmgr_data_o.good))

  // Check that pwrmgr_data_o.done is never de-asserted once asserted
  `ASSERT(PwrmgrDataChk_A, $rose(pwrmgr_data_o.done == prim_mubi_pkg::MuBi4True) |->
          always !$fell(pwrmgr_data_o.done == prim_mubi_pkg::MuBi4True),
          clk_i, !rst_ni || internal_alert)

  // Check that keymgr_data_o.valid is never de-asserted once asserted
  `ASSERT(KeymgrValidChk_A, $rose(keymgr_data_o.valid) |-> always !$fell(keymgr_data_o.valid),
          clk_i, !rst_ni || internal_alert)

  // Check that rom_tl_o.d_valid is not asserted unless pwrmgr_data_o.done is asseterd.
  // This check ensures that all tl accesses are blocked until rom check is completed. You might
  // think we could check for a_ready, but that doesn't work because the TL to SRAM adapter has a
  // 1-entry cache that accepts the transaction (but doesn't reply)
  `ASSERT(TlAccessChk_A,
          (pwrmgr_data_o.done == prim_mubi_pkg::MuBi4False) |->
          (!rom_tl_o.d_valid || (rom_tl_o.d_valid && rom_tl_o.d_error)))

  // Check that whenever there is an alert triggered and FSM state is Invalid, there is no response
  // to read requests.
  if (!SecDisableScrambling) begin : gen_fsm_scramble_enabled_asserts

    `ASSERT(BusLocalEscChk_A,
            (gen_fsm_scramble_enabled.u_checker_fsm.state_d == rom_ctrl_pkg::Invalid)
            |-> always(!bus_rom_rvalid))
  end

  // Alert assertions for reg_we onehot check
  `ASSERT_PRIM_REG_WE_ONEHOT_ERROR_TRIGGER_ALERT(RegWeOnehotCheck_A,
                                                 u_reg_regs, alert_tx_o[AlertFatal])

  // Alert assertions for redundant counters.
  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FifoWptrCheck_A,
      u_tl_adapter_rom.u_rspfifo.gen_normal_fifo.u_fifo_cnt.gen_secure_ptrs.u_wptr,
      alert_tx_o[AlertFatal])
  `ASSERT_PRIM_COUNT_ERROR_TRIGGER_ALERT(FifoRptrCheck_A,
      u_tl_adapter_rom.u_rspfifo.gen_normal_fifo.u_fifo_cnt.gen_secure_ptrs.u_rptr,
      alert_tx_o[AlertFatal])
endmodule
